<?php

namespace Core\annotationhandlers;

use Core\annotations\Lock;
use Core\annotations\Redis;
use Core\BeanFactory;
use Core\init\DecoratorCollector;
use Core\lib\RedisHelper;

/**
 * 取得Key
 * @param string $key
 * @param array $params
 * @return mixed|string
 */
function getKey(string $key, array $params)
{
    if (preg_match("/^#(\d+)/i", $key, $matches)) {
        return $params[$matches[1]];
    }
    return $key;
}

/**
 * 设置锁
 * @param Lock $self
 * @param $params
 * @return mixed
 */
function getLock(Lock $self, $params)
{
    $script = <<<LUA
    local key = KEYS[1]
    local expire = ARGV[1]
    if redis.call('setnx', key, 1) == 1 then 
        return redis.call('expire', key, expire)
    end
    return 0 
LUA;

    return RedisHelper::eval($script, [$self->prefix . getKey($self->key, $params), $self->expire], 1);
}

/**
 * 删除锁
 * @param Lock $self
 * @param $params
 * @return mixed
 */
function delLock(Lock $self, $params)
{
    $script = <<<LUA
    local key = KEYS[1]
    return redis.call('del', key)
LUA;

    return RedisHelper::eval($script, [$self->prefix . getKey($self->key, $params)], 1);
}

/**
 * 上锁
 */
function lock(Lock $self, $params)
{
    $retry = $self->retry;
    while ($retry-- > 0) {
        $getLock = getLock($self, $params);
        if ($getLock) {
            return true;
        }

        usleep(1000 * 100 * 1); //休眠100毫秒
    }
    return false;
}

/**
 * 跑起来
 * @param Lock $self
 * @param $params
 * @return  bool
 */
function run(Lock $self, $params, callable $func)
{
    try {
        if (lock($self, $params)) {  //获取锁成功
            $result = call_user_func($func, ... $params);  //执行业务
            delLock($self, $params); //删除锁
            return $result;
        }

        return false;

    } catch (\Throwable $e) {  //发生异常 删除锁
        delLock($self, $params);
        return false;
    }
}

return [
    Redis::class => function (\ReflectionMethod $method, $instance, $self) {

        /**
         * @var  $d_collector  DecoratorCollector
         */
        $d_collector = BeanFactory::getBean(DecoratorCollector::class);
        $key = (get_class($instance) . '::' . $method->getName());

        //收集装饰器，放入装饰器收集类
        $d_collector->dSet[$key] = function ($func) use ($self) {
            return function ($params) use ($func, $self) {
                if ($self->key != '') {
                    //执行
                    $res = run($self, $params, $func);
                    if ($res === false) {
                        return " data locked ";
                    } else {
                        return $res;
                    }

                }
                return call_user_func($func, ... $params);
            };
        };

        return $instance;
    }
];